package br.com.intelsys.utils.basics.webbrowser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.httpclient.Cookie;
import org.apache.log4j.Logger;

import br.com.intelsys.basics.utils.Knife;
import br.com.intelsys.utils.basics.webbrowser.IWebRequest.RequestMethod;

public class IWebBrowser {

	private Map<String, Cookie> cookieMap;
	private Proxy proxy;

	private static Logger log = Logger.getLogger(IWebBrowser.class);

	public IWebBrowser addCookie(String name, String value) {
		Cookie c = new Cookie();
		c.setName(name);
		c.setValue(value);
		addCookie(c);
		return this;
	}

	public IWebBrowser addCookie(Cookie cookie) {
		if (cookie != null && cookie.getName() != null) {
			if (cookieMap == null) {
				cookieMap = new HashMap<String, Cookie>();
			}
			if (cookie.getValue() != null && cookie.getValue().length() > 0) {
				cookieMap.put(cookie.getName(), cookie);
			} else {
				cookieMap.remove(cookie.getName());
			}
		}
		return this;
	}

	public IWebBrowser removeCookie(String name) {
		if (cookieMap != null) {
			cookieMap.remove(name);
		}
		return this;
	}

	public IWebBrowser proxy(Proxy proxy) {
		this.proxy = proxy;
		return this;
	}

	private void prepareIWebRequest(IWebRequest iWebRequest) {
		Map<String, Cookie> cookieMap = iWebRequest.getCookieMap();
		for (Entry<String, Cookie> entry : cookieMap.entrySet()) {
			if (entry.getValue().getValue() != null && entry.getValue().getValue().length() > 0) {
				addCookie(entry.getValue());
			} else {
				removeCookie(entry.getKey());
			}
		}
	}

	public IWebResponse doCall(IWebRequest iWebRequest) throws IWebException {
		prepareIWebRequest(iWebRequest);
		StringBuilder sb = new StringBuilder();
		IWebResponse resp = new IWebResponse(iWebRequest);
		URL url;
		try {
			url = iWebRequest.getURL();
		} catch (MalformedURLException e) {
			throw new IWebException("URL incorreta" + iWebRequest.getUrlStr(), e, resp);
		}
		try {
			String strRequestMethod = iWebRequest.getRequestMethod().toString();
			if (log.isDebugEnabled()) {
				log.debug("Acessando [" + strRequestMethod + "]: " + url);
			}

			URLConnection urlConn;
			if (this.proxy != null) {
				urlConn = url.openConnection(this.proxy);
			} else {
				urlConn = url.openConnection();
			}

			if (strRequestMethod != null) {
				((HttpURLConnection) urlConn).setRequestMethod(strRequestMethod);
			}

			addUrlConnHeader(urlConn, url, iWebRequest);

			String postData = null;

			if (strRequestMethod == RequestMethod.POST.toString()) {
				postData = iWebRequest.getParamPost();
				if (log.isDebugEnabled()) {
					log.debug("Postando: " + postData);
				}

				((HttpURLConnection) urlConn).setRequestMethod(strRequestMethod);
				urlConn.setDoOutput(true);
				if (iWebRequest.getRequestBody() != null && iWebRequest.getRequestBody().length() > 0) {
					OutputStreamWriter wr = new OutputStreamWriter(urlConn.getOutputStream());
					wr.write(iWebRequest.getRequestBody());
					wr.flush();
				} else if (iWebRequest.getRequestBodyInputStream() != null) {
					OutputStreamWriter wr = new OutputStreamWriter(urlConn.getOutputStream());
					wr.write(iWebRequest.getRequestBodyInputStream().read());
					wr.flush();
				} else if (postData != null) {
					OutputStreamWriter wr = new OutputStreamWriter(urlConn.getOutputStream());
					wr.write(postData);
					wr.flush();
				}
			}

			if (strRequestMethod != null && strRequestMethod == RequestMethod.DELETE.toString()) {
				urlConn.setDoOutput(true);
				urlConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
				urlConn.connect();

			}

			BufferedReader br = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
			String ln;
			while ((ln = br.readLine()) != null) {
				sb.append(ln);
			}
			extractAditionalResponseData(urlConn, resp);

		} catch (UnknownHostException e) {
			throw new IWebException("Erro de DNS ao solicitar a URL " + url, e, resp);
		} catch (MalformedURLException e) {
			throw new IWebException("Erro ao solicitar a URL " + url, e, resp);
		} catch (IOException e) {
			if (e.getMessage() != null && e.getMessage().startsWith("Server returned HTTP response code: 400 for URL:")) {
				throw new IWebException("Erro ao solicitar a URL " + url, e, resp);
			}
			Throwable t = e;
			Throwable lastElement = e;
			while (t != null) {
				t = t.getCause();
				if (t != null) {
					lastElement = t;
				}
			}
			if (lastElement.getClass().getName().startsWith("java.net") || lastElement.getClass().getName().startsWith("java.io")) {
				throw new IWebException("Erro ao solicitar a URL " + url, e, resp);
			}
			throw new IWebException("Erro ao solicitar a URL " + url, e, resp);
		} catch (Error e) {
			throw new IWebException("Erro DESCONHECIDO ao solicitar a URL " + url, e, resp);
		}
		resp.setResponseContent(sb.toString());
		return resp;

	}

	private void addUrlConnHeader(URLConnection urlConn, URL url, IWebRequest iWebRequest) {
		((HttpURLConnection) urlConn).setInstanceFollowRedirects(iWebRequest.isAutoFollowLocation());
		urlConn.addRequestProperty("Host", url.getHost());
		if (log.isDebugEnabled()) {
			log.debug("Sending header: Host: " + url.getHost());
		}
		for (Entry<String, List<String>> entry : iWebRequest.getParamHeaderMap().entrySet()) {
			for (String value : entry.getValue()) {
				urlConn.addRequestProperty(entry.getKey(), value);
				if (log.isDebugEnabled()) {
					log.debug("Sending header: " + entry.getKey() + ": " + value);
				}
			}
		}

		if (cookieMap != null) {
			Map<String, String> cMap = new HashMap<String, String>();
			for (Entry<String, Cookie> entry : cookieMap.entrySet()) {
				cMap.put(entry.getKey(), entry.getValue().getValue());
				if (log.isDebugEnabled()) {
					log.debug("Sending cookie: " + entry.getKey() + " = " + entry.getValue().getValue());
				}
			}
			urlConn.addRequestProperty("Cookie", IWebRequest.extractRawData(cMap, ';', iWebRequest.getCharSet()));
		}

	}

	private void extractAditionalResponseData(URLConnection urlConn, IWebResponse iWebResponse) {
		Map<String, List<String>> headerMap = urlConn.getHeaderFields();
		iWebResponse.setHeaderMap(headerMap);
		for (Entry<String, List<String>> ent : headerMap.entrySet()) {
			for (String val : ent.getValue()) {
				String headerLower = ent.getKey() != null ? ent.getKey().toLowerCase() : null;
				if (headerLower != null && headerLower.equals("set-cookie")) {
					addCookie(val, iWebResponse);
				} else {
					for (String h : ent.getValue()) {
						log.debug("Received Header: " + ent.getKey() + ": " + h);
					}
				}
			}
		}
	}

	private void addCookie(String cookieContent, IWebResponse iWebResponse) {
		if (cookieContent != null && cookieContent.length() > 1) {
			String[] cookieArr = cookieContent.split("=");
			if (cookieArr.length > 1) {
				if (cookieArr[0] != null && cookieArr[0].length() > 0) {
					String[] valueArr = Knife.explode(Knife.implode(cookieArr, '=', 1), ";");
					Cookie cookie = new Cookie();
					cookie.setName(cookieArr[0]);
					cookie.setValue(valueArr[0]);
					SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss zzz", Locale.US);
					for (int i = 1; i < valueArr.length; i++) {
						String[] paramArr = Knife.explode(valueArr[i], "=");
						if (paramArr.length > 1) {
							String paramName = paramArr[0].trim();
							if ("path".equals(paramName)) {
								cookie.setPath(paramArr[1]);
							} else if ("domain".equals(paramName)) {
								cookie.setDomain(paramArr[1]);
							} else if ("expires".equals(paramName)) {
								try {
									cookie.setExpiryDate(sdf.parse(paramArr[1]));
								} catch (ParseException e) {
									log.error(e);
								}
							}
						}
					}
					if (valueArr[0] != null && valueArr[0].length() > 0 && !"deleted".equals(valueArr[0])) {
						addCookie(cookie);
						iWebResponse.addCookie(cookie);
						if (log.isDebugEnabled()) {
							log.debug("Received cookie: " + cookie.getName() + " = " + cookie.getValue());
						}
					} else {
						removeCookie(cookie.getName());
						if (log.isDebugEnabled()) {
							log.debug("Removed cookie: " + cookie.getName());
						}
					}
				}
			}
		}
	}
}
